"""微信公众平台API适配器实现"""

import time
import hashlib
import hmac
import json
from typing import Dict, Any, Optional, List, Union
from urllib.parse import urlencode
from ..common.base import BaseAPI
from .exceptions import (
    WeChatError,
    WeChatAccessTokenError,
    WeChatAuthError,
    WeChatAPIQuotaError
)


class WeChatAPI(BaseAPI):
    """
    微信公众平台API适配器
    支持微信公众号、小程序等平台的API调用
    """
    
    def __init__(self,
                 appid: str,
                 appsecret: str,
                 base_url: str = "https://api.weixin.qq.com",
                 timeout: int = 30,
                 max_retries: int = 3,
                 retry_delay: int = 1):
        """
        初始化微信API适配器
        
        Args:
            appid: 微信公众号/小程序的AppID
            appsecret: 微信公众号/小程序的AppSecret
            base_url: API基础URL，默认为官方API地址
            timeout: 请求超时时间（秒）
            max_retries: 最大重试次数
            retry_delay: 重试间隔（秒）
        """
        super().__init__(base_url, timeout, max_retries, retry_delay)
        self.appid = appid
        self.appsecret = appsecret
        self._access_token = None
        self._access_token_expire_time = 0
    
    def _handle_response(self, response: 'requests.Response') -> Dict[str, Any]:
        """
        处理微信API响应，特定处理微信的错误码
        
        Args:
            response: requests.Response对象
            
        Returns:
            解析后的响应数据
            
        Raises:
            WeChatError: 微信API调用错误
        """
        try:
            data = response.json()
        except ValueError:
            # 非JSON响应
            data = {"content": response.text}
        
        # 微信API特有的错误处理
        if "errcode" in data and data["errcode"] != 0:
            errcode = data["errcode"]
            errmsg = data.get("errmsg", "未知错误")
            
            # 特定错误码处理
            if errcode == 40001:
                # AccessToken无效或已过期
                self._access_token = None
                self._access_token_expire_time = 0
                raise WeChatAccessTokenError(f"AccessToken错误: {errmsg}", errcode, response)
            elif errcode == 45009:
                # API调用频率限制
                raise WeChatAPIQuotaError(f"API调用频率限制: {errmsg}", errcode, response)
            elif errcode in [40002, 40013, 41001, 41002, 41003, 41004]:
                # 认证相关错误
                raise WeChatAuthError(f"认证错误: {errmsg}", errcode, response)
            else:
                # 其他微信错误
                raise WeChatError(f"微信API错误: {errmsg}", errcode, response)
        
        # HTTP状态码错误处理
        if response.status_code >= 400:
            super()._handle_response(response)
        
        return data
    
    def get_access_token(self, force_refresh: bool = False) -> str:
        """
        获取或刷新AccessToken
        
        Args:
            force_refresh: 是否强制刷新
            
        Returns:
            AccessToken字符串
            
        Raises:
            WeChatAccessTokenError: AccessToken获取失败
        """
        # 检查AccessToken是否存在且未过期
        current_time = time.time()
        if (not force_refresh and 
            self._access_token and 
            current_time < self._access_token_expire_time):
            return self._access_token
        
        # 获取新的AccessToken
        endpoint = "cgi-bin/token"
        params = {
            "grant_type": "client_credential",
            "appid": self.appid,
            "secret": self.appsecret
        }
        
        try:
            response = super().get(endpoint, params=params)
            
            # 更新AccessToken和过期时间
            self._access_token = response["access_token"]
            # 过期时间减去10分钟作为缓冲
            expires_in = response.get("expires_in", 7200) - 600
            self._access_token_expire_time = current_time + expires_in
            
            return self._access_token
        except Exception as e:
            if not isinstance(e, WeChatAccessTokenError):
                raise WeChatAccessTokenError(f"获取AccessToken失败: {str(e)}") from e
            raise
    
    def _request(self, *args, **kwargs) -> Dict[str, Any]:
        """
        重写请求方法，自动添加AccessToken
        """
        # 检查是否需要添加AccessToken（非获取AccessToken自身的请求）
        if len(args) >= 2 and args[1] != "cgi-bin/token":
            # 确保有params参数
            if "params" not in kwargs:
                kwargs["params"] = {}
            
            # 如果params中没有access_token，则自动添加
            if "access_token" not in kwargs["params"]:
                kwargs["params"]["access_token"] = self.get_access_token()
        
        try:
            return super()._request(*args, **kwargs)
        except WeChatAccessTokenError:
            # AccessToken失效，尝试刷新并重试
            kwargs["params"]["access_token"] = self.get_access_token(force_refresh=True)
            return super()._request(*args, **kwargs)
    
    # === 消息相关接口 ===
    
    def send_text_message(self,
                         touser: str,
                         content: str,
                         safe: int = 0) -> Dict[str, Any]:
        """
        发送文本消息
        
        Args:
            touser: 接收者的openid
            content: 文本消息内容，最长不超过2048个字节
            safe: 表示是否是保密消息，0表示否，1表示是，默认0
            
        Returns:
            API响应结果
        """
        endpoint = "cgi-bin/message/custom/send"
        data = {
            "touser": touser,
            "msgtype": "text",
            "text": {
                "content": content
            },
            "safe": safe
        }
        return self.post(endpoint, json_data=data)
    
    def send_image_message(self,
                          touser: str,
                          media_id: str,
                          safe: int = 0) -> Dict[str, Any]:
        """
        发送图片消息
        
        Args:
            touser: 接收者的openid
            media_id: 图片的媒体ID
            safe: 表示是否是保密消息，0表示否，1表示是，默认0
            
        Returns:
            API响应结果
        """
        endpoint = "cgi-bin/message/custom/send"
        data = {
            "touser": touser,
            "msgtype": "image",
            "image": {
                "media_id": media_id
            },
            "safe": safe
        }
        return self.post(endpoint, json_data=data)
    
    # === 用户相关接口 ===
    
    def get_user_info(self, openid: str, lang: str = "zh_CN") -> Dict[str, Any]:
        """
        获取用户基本信息
        
        Args:
            openid: 用户的openid
            lang: 返回国家地区语言版本，zh_CN 简体，zh_TW 繁体，en 英语
            
        Returns:
            用户信息
        """
        endpoint = "cgi-bin/user/info"
        params = {
            "openid": openid,
            "lang": lang
        }
        return self.get(endpoint, params=params)
    
    def get_user_list(self, next_openid: str = "") -> Dict[str, Any]:
        """
        获取用户列表
        
        Args:
            next_openid: 下一个用户的openid，默认为空表示从头开始
            
        Returns:
            用户列表信息
        """
        endpoint = "cgi-bin/user/get"
        params = {
            "next_openid": next_openid
        }
        return self.get(endpoint, params=params)
    
    # === 素材管理接口 ===
    
    def upload_media(self,
                    media_type: str,
                    media_file: str,
                    is_temporary: bool = True) -> Dict[str, Any]:
        """
        上传媒体文件
        
        Args:
            media_type: 媒体文件类型，可选值：image, voice, video, thumb
            media_file: 媒体文件路径
            is_temporary: 是否是临时素材，默认为True
            
        Returns:
            上传结果，包含media_id
        """
        if is_temporary:
            endpoint = f"cgi-bin/media/upload"
            params = {"type": media_type}
        else:
            endpoint = f"cgi-bin/material/add_material"
            params = {"type": media_type}
        
        with open(media_file, 'rb') as f:
            files = {'media': f}
            return self.post(endpoint, params=params, files=files)
    
    # === 菜单管理接口 ===
    
    def create_menu(self, menu_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        创建自定义菜单
        
        Args:
            menu_data: 菜单数据
            
        Returns:
            API响应结果
        """
        endpoint = "cgi-bin/menu/create"
        return self.post(endpoint, json_data=menu_data)
    
    def get_menu(self) -> Dict[str, Any]:
        """
        获取自定义菜单
        
        Returns:
            菜单数据
        """
        endpoint = "cgi-bin/menu/get"
        return self.get(endpoint)
    
    def delete_menu(self) -> Dict[str, Any]:
        """
        删除自定义菜单
        
        Returns:
            API响应结果
        """
        endpoint = "cgi-bin/menu/delete"
        return self.get(endpoint)
    
    # === 二维码相关接口 ===
    
    def create_qrcode(self,
                     scene_data: Dict[str, Any],
                     expire_seconds: int = None) -> Dict[str, Any]:
        """
        创建二维码
        
        Args:
            scene_data: 场景数据，如 {"scene_id": 123} 或 {"scene_str": "test"}
            expire_seconds: 二维码过期时间（秒），永久二维码不需要此参数
            
        Returns:
            二维码ticket和URL
        """
        endpoint = "cgi-bin/qrcode/create"
        
        data = {}
        if expire_seconds:
            # 临时二维码
            data = {
                "expire_seconds": expire_seconds,
                "action_name": "QR_SCENE",
                "action_info": {
                    "scene": scene_data
                }
            }
        else:
            # 永久二维码
            if "scene_id" in scene_data:
                data = {
                    "action_name": "QR_LIMIT_SCENE",
                    "action_info": {
                        "scene": scene_data
                    }
                }
            elif "scene_str" in scene_data:
                data = {
                    "action_name": "QR_LIMIT_STR_SCENE",
                    "action_info": {
                        "scene": scene_data
                    }
                }
        
        return self.post(endpoint, json_data=data)
    
    def get_qrcode_url(self, ticket: str) -> str:
        """
        根据ticket获取二维码图片URL
        
        Args:
            ticket: 二维码ticket
            
        Returns:
            二维码图片URL
        """
        return f"https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={ticket}"
    
    # === JS-SDK相关接口 ===
    
    def get_jsapi_ticket(self, force_refresh: bool = False) -> str:
        """
        获取JSAPI Ticket
        
        Args:
            force_refresh: 是否强制刷新
            
        Returns:
            JSAPI Ticket字符串
        """
        # 如果没有jsapi_ticket属性或已过期，则获取新的
        if (not hasattr(self, '_jsapi_ticket') or 
            not hasattr(self, '_jsapi_ticket_expire_time') or
            force_refresh or
            time.time() >= self._jsapi_ticket_expire_time):
            
            endpoint = "cgi-bin/ticket/getticket"
            params = {
                "type": "jsapi"
            }
            
            response = self.get(endpoint, params=params)
            
            self._jsapi_ticket = response["ticket"]
            # 过期时间减去10分钟作为缓冲
            expires_in = response.get("expires_in", 7200) - 600
            self._jsapi_ticket_expire_time = time.time() + expires_in
        
        return self._jsapi_ticket
    
    def get_jsapi_signature(self, url: str) -> Dict[str, str]:
        """
        获取JSAPI签名
        
        Args:
            url: 需要签名的URL（不包含#及其后面部分）
            
        Returns:
            签名信息
        """
        import random
        
        # 获取noncestr
        noncestr = ''.join(random.choices('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=16))
        
        # 获取timestamp
        timestamp = str(int(time.time()))
        
        # 获取jsapi_ticket
        jsapi_ticket = self.get_jsapi_ticket()
        
        # 拼接签名字符串
        signature_params = [
            f"jsapi_ticket={jsapi_ticket}",
            f"noncestr={noncestr}",
            f"timestamp={timestamp}",
            f"url={url}"
        ]
        signature_str = '&'.join(signature_params)
        
        # 计算签名
        signature = hashlib.sha1(signature_str.encode('utf-8')).hexdigest()
        
        return {
            "appId": self.appid,
            "nonceStr": noncestr,
            "timestamp": timestamp,
            "signature": signature
        }
    
    # === 消息推送相关接口 ===
    
    def send_template_message(self,
                            touser: str,
                            template_id: str,
                            data: Dict[str, Any],
                            url: str = "",
                            miniprogram: Dict[str, str] = None) -> Dict[str, Any]:
        """
        发送模板消息
        
        Args:
            touser: 接收者的openid
            template_id: 模板消息ID
            data: 模板数据，格式为 {"key": {"value": "...", "color": "..."}}
            url: 点击模板卡片跳转的URL
            miniprogram: 跳转到小程序的信息，格式为 {"appid": "...", "pagepath": "..."}
            
        Returns:
            API响应结果
        """
        endpoint = "cgi-bin/message/template/send"
        message_data = {
            "touser": touser,
            "template_id": template_id,
            "data": data
        }
        
        if url:
            message_data["url"] = url
        if miniprogram:
            message_data["miniprogram"] = miniprogram
        
        return self.post(endpoint, json_data=message_data)